package com.dnjn.mes.biz.modbus;

import com.intelligt.modbus.jlibmodbus.Modbus;
import com.intelligt.modbus.jlibmodbus.data.DataHolder;
import com.intelligt.modbus.jlibmodbus.data.ModbusCoils;
import com.intelligt.modbus.jlibmodbus.data.ModbusHoldingRegisters;
import com.intelligt.modbus.jlibmodbus.exception.IllegalDataAddressException;
import com.intelligt.modbus.jlibmodbus.exception.IllegalDataValueException;
import com.intelligt.modbus.jlibmodbus.slave.ModbusSlave;
import com.intelligt.modbus.jlibmodbus.slave.ModbusSlaveFactory;
import com.intelligt.modbus.jlibmodbus.tcp.TcpParameters;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;

@Profile({"test", "dev", "local"})
@Slf4j
@Component("modbusSlaveHelper")
public class ModbusSlaveHelper {


    @Async
    public void startModbusSlave() throws Exception {
        log.info("start modbus slave..");
        InetAddress localHost = InetAddress.getLocalHost();
        log.info("localHost {}", localHost);
        TcpParameters parameters = new TcpParameters();
//        InetAddress address = InetAddress.getByName("127.0.0.1");
//        parameters.setHost(address);
        parameters.setHost(localHost);//?

        //parameters.setPort(Modbus.TCP_PORT); //502
        //在linux系统上只有root用户才可以访问1024以下的端口号，其余用户不能访问。
        parameters.setPort(1502); //502
        parameters.setKeepAlive(true);
        ModbusSlave slave = ModbusSlaveFactory.createModbusSlaveTCP(parameters);
        Modbus.setLogLevel(Modbus.LogLevel.LEVEL_DEBUG);

        MyOwnDataHolder db = new MyOwnDataHolder();
        db.addEventListener(new ModbusEventListener() {
            @Override
            public void onWriteToSingleColi(int address, boolean value) {

                log.info("address {}, value {}", address, value);
            }

            @Override
            public void onWriteToMultipleCoils(int address, int quantity, boolean[] values) {
                log.info("address {}, quantity {}", address, quantity);
            }

            @Override
            public void onWriteToSingleHoldingRegister(int address, int values) {
                log.info("address {}, quantity {}", address, values);
            }

            @Override
            public void onWriteToMultipleHoldingRegisters(int address, int quantity, int[] values) {
                log.info("address {}, quantity {}", address, quantity);
            }
        });

        slave.setDataHolder(db);
        slave.setReadTimeout(1500);
        ModbusHoldingRegisters hr = new ModbusHoldingRegisters(600);
        hr.set(0, 12);
        hr.set(1, 24);
        for (int i = 3; i < 600; i++) {
            hr.set(i, 100 + 1);
        }
        ModbusCoils mc = new ModbusCoils(16);
        mc.set(0, true);

        slave.getDataHolder().setInputRegisters(hr);
        slave.getDataHolder().setCoils(mc);
        slave.setServerAddress(1);
        slave.listen();
        if (slave.isListening()) {
            Runtime.getRuntime().addShutdownHook(new Thread() {
                @Override
                public void run() {
//                    super.run();
                    synchronized (slave) {
                        slave.notifyAll();
                    }
                }
            });

            synchronized (slave) {
                slave.wait();
                slave.shutdown();
                log.info("shutdown modbus slave");
            }
        }

    }


    public interface ModbusEventListener {
        void onWriteToSingleColi(int address, boolean value);

        void onWriteToMultipleCoils(int address, int quantity, boolean[] values);

        void onWriteToSingleHoldingRegister(int address, int values);

        void onWriteToMultipleHoldingRegisters(int address, int quantity, int[] values);

    }

    public static class MyOwnDataHolder extends DataHolder {
        final List<ModbusEventListener> modbusEventListenerList = new ArrayList<>();

        public MyOwnDataHolder() {
            //
        }

        public void addEventListener(ModbusEventListener modbusEventListener) {
            modbusEventListenerList.add(modbusEventListener);
        }

        public boolean removeEventListener(ModbusEventListener modbusEventListener) {
            return modbusEventListenerList.remove(modbusEventListener);
        }

        @Override
        public void writeHoldingRegister(int offset,
                                         int value) throws IllegalDataAddressException, IllegalDataValueException {

            for (ModbusEventListener modbusEventListener : modbusEventListenerList) {
                modbusEventListener.onWriteToSingleHoldingRegister(offset, value);
            }
            super.writeHoldingRegister(offset, value);
        }

        @Override
        public void writeHoldingRegisterRange(int offset,
                                              int[] range) throws IllegalDataAddressException, IllegalDataValueException {
            for (ModbusEventListener modbusEventListener : modbusEventListenerList) {
                modbusEventListener.onWriteToMultipleHoldingRegisters(offset, range.length, range);
            }
            super.writeHoldingRegisterRange(offset, range);
        }

        @Override
        public void writeCoil(int offset, boolean value) throws IllegalDataAddressException, IllegalDataValueException {

            for (ModbusEventListener modbusEventListener : modbusEventListenerList) {
                modbusEventListener.onWriteToSingleColi(offset, value);
            }

            super.writeCoil(offset, value);
        }

        @Override
        public void writeCoilRange(int offset,
                                   boolean[] range) throws IllegalDataAddressException, IllegalDataValueException {

            for (ModbusEventListener modbusEventListener : modbusEventListenerList) {
                modbusEventListener.onWriteToMultipleCoils(offset, range.length, range);
            }
            super.writeCoilRange(offset, range);
        }
    }
}
